Node-RED block in Snap4City Microservice library node-red-contrib-snap4city-d3-dashboard-widgets is "Snap4D3">

With this node you can add a custom D3 widget to an existing dashboard or a new one created by the node.

Input

The data to be sent to the widget

Configuration

ATTENTION string
This node accepts a D3 configuration as an input payload or as input directly on the node's form.
If the configuration is sent as a payload to the node, then the code in the node module is ignored, so the payload configuration takes precedence.
dashboard string
Dashboard title to which D3 widget will be added
widget string
Title of the D3 widget.
D3 configuration code string
JS function that encapsulate D3 native code
Template
                /**
                * 
                * @param  d3: instance of D3 library
                * @param  d3Data: data sent by node-red for rendering with D3.
                * @param  width : width of the widget display space
                * @param  height :height of the widget display space 
                * @param  sendToNodeRed: function used to send back data from widget to node-red
                * @returns a Promise that must be resolved with a D3 chart instance
                */
                async function drawD3Chart(d3,d3Data,width,height,sendToNodeRed){
                    // DON'T CHANGE the function name
                    // INSERT YOUR CODE HERE    
                }
            
D3 configuration code as input payload of Snap4D3 node string
JS code template to copy in a function node
Template
                
                const d3Configuration=String.raw`
                /**
                * 
                * @param  d3: instance of D3 library
                * @param  d3Data: data sent by node-red for rendering with D3.
                * @param  width : width of the widget display space
                * @param  height :height of the widget display space 
                * @param  sendToNodeRed: function used to send back data from widget to node-red
                * @returns a Promise that must be resolved with a D3 chart instance
                */
                async function drawD3Chart(d3,d3Data,width,height,sendToNodeRed){
                    // DON'T CHANGE the function name
                    // INSERT YOUR CODE HERE    
                }
                `;

                // ATTENTION: you should use the following line to send a D3 configuration to Snap4D3 node as an input payload
                msg["configuration"]=d3Configuration;
                return msg;
            

Output

Returns the data sent by the D3 widget

Example of D3 configuration as an input of node's form

        
        /**
        * 
        * @param  d3: instance of D3 library
        * @param  d3Data: data sent by node-red for rendering with D3.
        * @param  width : width of the widget display space
        * @param  height :height of the widget display space 
        * @param  sendToNodeRed: function used to send back data from widget to node-red
        * @returns D3 chart instance
        */
        async function drawD3Chart(d3,d3Data,width,height,sendToNodeRed){

            function BarChart(data, {
                x = (d, i) => i, // given d in data, returns the (ordinal) x-value
                y = d => d, // given d in data, returns the (quantitative) y-value
                title, // given d in data, returns the title text
                marginTop = 20, // the top margin, in pixels
                marginRight = 0, // the right margin, in pixels
                marginBottom = 30, // the bottom margin, in pixels
                marginLeft = 40, // the left margin, in pixels
                width = 640, // the outer width of the chart, in pixels
                height = 400, // the outer height of the chart, in pixels
                xDomain, // an array of (ordinal) x-values
                xRange = [marginLeft, width - marginRight], // [left, right]
                yType = d3.scaleLinear, // y-scale type
                yDomain, // [ymin, ymax]
                yRange = [height - marginBottom, marginTop], // [bottom, top]
                xPadding = 0.1, // amount of x-range to reserve to separate bars
                yFormat, // a format specifier string for the y-axis
                yLabel, // a label for the y-axis
                color = "currentColor" // bar fill color
            } = {}) {

                // Compute values.
                const X = d3.map(data, x);
                const Y = d3.map(data, y);
            
                // Compute default domains, and unique the x-domain.
                if (xDomain === undefined) {xDomain = X;}
                if (yDomain === undefined) {yDomain = [0, d3.max(Y)];}
                xDomain = new d3.InternSet(xDomain);
            
                // Omit any data not present in the x-domain.
                const I = d3.range(X.length).filter(i => xDomain.has(X[i]));
            
                // Construct scales, axes, and formats.
                const xScale = d3.scaleBand(xDomain, xRange).padding(xPadding);
                const yScale = yType(yDomain, yRange);
                const xAxis = d3.axisBottom(xScale).tickSizeOuter(0);
                const yAxis = d3.axisLeft(yScale).ticks(height / 40, yFormat);
            
                // Compute titles.
                if (title === undefined) {
                    const formatValue = yScale.tickFormat(100, yFormat);
                    title = i => `${X[i]}\n${formatValue(Y[i])}`;
                } else {
                    const O = d3.map(data, d => d);
                    const T = title;
                    title = i => T(O[i], i, data);
                }
            
                const svg = d3.create("svg")
                    .attr("width", width)
                    .attr("height", height)
                    .attr("viewBox", [0, 0, width, height])            
                    .attr("style", "max-width: 100%; height: auto; height: intrinsic;");
            
                svg.append("g")
                    .attr("transform", `translate(${marginLeft},0)`)
                    .call(yAxis)
                    .call(g => g.select(".domain").remove())
                    .call(g => g.selectAll(".tick line").clone()
                        .attr("x2", width - marginLeft - marginRight)
                        .attr("stroke-opacity", 0.1))
                    .call(g => g.append("text")
                        .attr("x", -marginLeft)
                        .attr("y", 10)
                        .attr("fill", "currentColor")
                        .attr("text-anchor", "start")
                        .text(yLabel));
            
                const bar = svg.append("g")
                    .attr("fill", color)
                    .selectAll("rect")
                    .data(I)
                    .join("rect")
                    .attr("x", i => xScale(X[i]))
                    .attr("y", i => yScale(Y[i]))
                    .attr("height", i => yScale(0) - yScale(Y[i]))
                    .attr("width", xScale.bandwidth())            
                    .on("click", (event, index) => {                
                        sendToNodeRed({x:X[index],y:Y[index]});
                    });
            
                if (title) {bar.append("title")
                    .text(title);}
            
                svg.append("g")
                    .attr("transform", `translate(0,${height - marginBottom})`)
                    .call(xAxis);       
            
                return svg.node(); //ATTENTION: THIS MUST BE RETURNED BY THE FUNCTION TO BE USED BY THE WIDGET
            }
            
            //ATTENTION: THIS BELOW MUST BE RETURNED BY THE FUNCTION TO BE USED BY THE WIDGET
            return BarChart(d3Data, {
                x: d => d.letter,
                y: d => d.frequency,
                xDomain: d3.groupSort(d3Data, ([d]) => -d.frequency, d => d.letter), // sort by descending frequency
                yFormat: "%",
                yLabel: "↑ Frequency",
                width,
                height,
                color: "steelblue"
            });
        
        }
           
    

Example of D3 configuration as an input payload of node

        const d3Configuration=String.raw`

            /**
            * 
            * @param  d3: instance of D3 library
            * @param  d3Data: data sent by node-red for rendering with D3.
            * @param  width : width of the widget display space
            * @param  height :height of the widget display space 
            * @param  sendToNodeRed: function used to send back data from widget to node-red
            * @returns D3 chart instance
            */
            async function drawD3Chart(d3,d3Data,width,height,sendToNodeRed){

                function BarChart(data, {
                    x = (d, i) => i, // given d in data, returns the (ordinal) x-value
                    y = d => d, // given d in data, returns the (quantitative) y-value
                    title, // given d in data, returns the title text
                    marginTop = 20, // the top margin, in pixels
                    marginRight = 0, // the right margin, in pixels
                    marginBottom = 30, // the bottom margin, in pixels
                    marginLeft = 40, // the left margin, in pixels
                    width = 640, // the outer width of the chart, in pixels
                    height = 400, // the outer height of the chart, in pixels
                    xDomain, // an array of (ordinal) x-values
                    xRange = [marginLeft, width - marginRight], // [left, right]
                    yType = d3.scaleLinear, // y-scale type
                    yDomain, // [ymin, ymax]
                    yRange = [height - marginBottom, marginTop], // [bottom, top]
                    xPadding = 0.1, // amount of x-range to reserve to separate bars
                    yFormat, // a format specifier string for the y-axis
                    yLabel, // a label for the y-axis
                    color = "currentColor" // bar fill color
                } = {}) {

                    // Compute values.
                    const X = d3.map(data, x);
                    const Y = d3.map(data, y);
                
                    // Compute default domains, and unique the x-domain.
                    if (xDomain === undefined) {xDomain = X;}
                    if (yDomain === undefined) {yDomain = [0, d3.max(Y)];}
                    xDomain = new d3.InternSet(xDomain);
                
                    // Omit any data not present in the x-domain.
                    const I = d3.range(X.length).filter(i => xDomain.has(X[i]));
                
                    // Construct scales, axes, and formats.
                    const xScale = d3.scaleBand(xDomain, xRange).padding(xPadding);
                    const yScale = yType(yDomain, yRange);
                    const xAxis = d3.axisBottom(xScale).tickSizeOuter(0);
                    const yAxis = d3.axisLeft(yScale).ticks(height / 40, yFormat);
                
                    // Compute titles.
                    if (title === undefined) {
                        const formatValue = yScale.tickFormat(100, yFormat);
                        title = i => X[i]+"\n"+formatValue(Y[i]);
                    } else {
                        const O = d3.map(data, d => d);
                        const T = title;
                        title = i => T(O[i], i, data);
                    }
                
                    const svg = d3.create("svg")
                        .attr("width", width)
                        .attr("height", height)
                        .attr("viewBox", [0, 0, width, height])            
                        .attr("style", "max-width: 100%; height: auto; height: intrinsic;");
                
                    svg.append("g")
                        .attr("transform", "translate("+marginLeft+",0)")
                        .call(yAxis)
                        .call(g => g.select(".domain").remove())
                        .call(g => g.selectAll(".tick line").clone()
                            .attr("x2", width - marginLeft - marginRight)
                            .attr("stroke-opacity", 0.1))
                        .call(g => g.append("text")
                            .attr("x", -marginLeft)
                            .attr("y", 10)
                            .attr("fill", "currentColor")
                            .attr("text-anchor", "start")
                            .text(yLabel));
                
                    const bar = svg.append("g")
                        .attr("fill", color)
                        .selectAll("rect")
                        .data(I)
                        .join("rect")
                        .attr("x", i => xScale(X[i]))
                        .attr("y", i => yScale(Y[i]))
                        .attr("height", i => yScale(0) - yScale(Y[i]))
                        .attr("width", xScale.bandwidth())            
                        .on("click", (event, index) => {                
                            sendToNodeRed({x:X[index],y:Y[index]});
                        });
                
                    if (title) {bar.append("title")
                        .text(title);}
                
                    svg.append("g")
                        .attr("transform", "translate(0,"+(height - marginBottom)+")")
                        .call(xAxis);       
                
                    return svg.node(); //ATTENTION: THIS MUST BE RETURNED BY THE FUNCTION TO BE USED BY THE WIDGET
                }
                
                //ATTENTION: THIS BELOW MUST BE RETURNED BY THE FUNCTION TO BE USED BY THE WIDGET
                return BarChart(d3Data, {
                    x: d => d.letter,
                    y: d => d.frequency,
                    xDomain: d3.groupSort(d3Data, ([d]) => -d.frequency, d => d.letter), // sort by descending frequency
                    yFormat: "%",
                    yLabel: "↑ Frequency",
                    width,
                    height,
                    color: "steelblue"
                });
                
            }
                `;

        msg["configuration"]=d3Configuration;
        
        return msg;